package implementacionJ48;

import java.io.File;
import java.lang.System;

import weka.core.converters.ConverterUtils.DataSource;

import weka.core.Instance;
import weka.core.Instances;
import weka.core.FastVector;


import weka.core.converters.ArffLoader;

import weka.classifiers.trees.j48.*;

import weka.classifiers.Classifier;

import weka.attributeSelection.*;
import weka.core.*;
import weka.classifiers.*;
import weka.classifiers.meta.*;
import weka.classifiers.trees.*;
import weka.filters.*;

import java.util.*;


public class TestData {

	private static DataSource source;
	private static Instances data;
	public static double valorOptimoParticion[];
	
	/* Se calcula el peso de una fila donde existe un valor desconocido */
	public static double calculateWeight(Instances S, Attribute attr, double attrValue) throws Exception{
		
		double cantAttrValue = 0.0;
		double cantMissingValues = 0.0;
		
		/* Calcula cuantas veces aparece un valor de un atributo en toda la tabla y cuantas veces hay un valor desconocido*/
		for (int j = 0; j < S.numInstances(); j++){
			Instance instanceWeight = S.instance(j);
			if(instanceWeight.value(attr) == attrValue){
				cantAttrValue++;
			}else{
				if(instanceWeight.isMissing(attr))
					cantMissingValues++;
			}
		}		
		/*System.out.println("-----");
		System.out.println(cantAttrValue/(S.numInstances()- cantMissingValues));

		System.out.println("-----");*/
		
		return cantAttrValue/(S.numInstances() - cantMissingValues);
		
	}
	
	public static double getValorOptimoParticion(int indiceAtributo){
		
		return valorOptimoParticion[indiceAtributo];
		
	}
	
	/* Chequea si existen valores numericos en cada una de las columnas de atributos */
	public static boolean existNumericAttribute(Instances S){
		int cantAtributos = S.numAttributes();
		
		for(int i = 0; i < cantAtributos; i++){
			if(S.attribute(i).isNumeric())
				return true;
		}
		
		return false;
	}
	
	/* Setea los atributos con respecto al valor optimo Z */
	public static void setAttributeToNumeric(Instances S, Attribute attr, double Z){
		for(int i = 0; i < S.numInstances(); i++){
			if(!S.instance(i).isMissing(attr)){
				if(S.instance(i).value(attr) <= Z)
					S.instance(i).setValue(attr, 0.0);
				else
					S.instance(i).setValue(attr, 1.0);
			}
		}
		
	}
	
	/* Chequea si en una columna hay valores numericos */
	public static boolean isNumericAttribute(Attribute attr){
		//int cantAtributos = S.numAttributes();
		
//		for(int i = 0; i < cantAtributos; i++){
		if(attr.isNumeric())
			return true;
	//	}
		
		return false;
	}

	/* */
	public static double getValorOptimoParticion(Instances S, Attribute attr) throws Exception{

		int cantFilasTotal = S.attributeStats(0).totalCount;
		double valoresNumericosAttr[] = new double[cantFilasTotal];
		double valoresGanancia[] = new double[cantFilasTotal];
		double valorOptimo = 0.0;

		/* Hacemos una copia exacta del conjunto de instancias S */
		Instances InstancesPrueba = new Instances(S);
		/* Obtenemos todos los valores de la columna attr*/
		for(int i = 0; i < cantFilasTotal; i++){
			valoresNumericosAttr[i] = InstancesPrueba.instance(i).value(attr);
		}
		
		/* Chequeamos la ganancia de cada uno de los valores de particion */
		for(int i = 0; i < cantFilasTotal; i++){
			InstancesPrueba = new Instances(S);
			/* Cambiamos S con los nuevos posibles valores en su columna */
			setAttributeToNumeric(InstancesPrueba, attr, valoresNumericosAttr[i]);
			valoresGanancia[i] = Gain.getGanancia(InstancesPrueba, attr);
			//valoresGanancia[i] = Gain.getProporcionGanancia(InstancesPrueba, attr);
		}
		
		/* Encontramos el valor optimo de particion*/
		int iter = 0;
		for(int i = 0; i < cantFilasTotal; i++){
			if(valorOptimo < valoresGanancia[i]){
				valorOptimo = valoresGanancia[i];
				iter = i;
			}
		}
		
		//System.out.println("------------------------------------------");
		
		/* Este es para el caso de que isMissing un valor y ese sea el mejor valor de los probados */
		if(S.instance(iter).isMissing(attr))
			return 0.0;
		
		return S.instance(iter).value(attr);
	}
	
	/* Carga las Instancias de un archivo ARFF */
	public static Instances getfromArff(String path) throws Exception {
		 source = new DataSource(path);
		 data = source.getDataSet();
			
		 /* Inicializamos el vector donde guardaremos el valorOptimoParticion para cada posible columna
		  * con valores numericos */
		 valorOptimoParticion = new double[data.numAttributes()];
		 
		 if (data.classIndex() == -1)
		   data.setClassIndex(data.numAttributes() - 1);
		 
		 /* Si hay datos numericos en las columnas de los atributos hacemos lso cambios necesarios */
		 if( existNumericAttribute(data) ){
			 /* Recorremos cada columna para hacer los cambios */
			 for(int i = 0; i < data.numAttributes(); i++){
				 if(isNumericAttribute(data.attribute(i))){
					 double valorOptimo = getValorOptimoParticion(data, data.attribute(i));
					 setAttributeToNumeric(data, data.attribute(i), valorOptimo);
					 /* Seteamos el valorOptimoParticion dentro del vector */
					 valorOptimoParticion[i] = valorOptimo;
				 }
			 }
		 }
		 System.out.println(valorOptimoParticion.toString());
		 
		 return data;
	}

	/* Retorna un nuevo conjunto de instancias, en donde el atributo attr tiene el valor de attributeValue */
	public static Instances selectInstancesWhere(Instances S, Attribute attr, double attributeValue){
		int cantFilasTotal = S.attributeStats(0).totalCount;
		/* Este es el indice del atributo dentro de la tabla del arff*/
		int attrIndex = attr.index();
		
		/* Hacemos una copia exacta del conjunto de instancias S */
		Instances selectedInstances = new Instances(S);

		for(int i = 0; i < cantFilasTotal; i++){
			Instance instancia = selectedInstances.instance(i);
			if(instancia != null){
				/* Si la instancia tiene un valor diferente a attributeValue, borramos de la lista(selectedInstances) */
				if( (instancia.value(instancia.attribute(attrIndex)) != attributeValue) ){
						selectedInstances.delete(i);
						i--;
				}
			}else{
				i = cantFilasTotal;
			}
		}
		
		return selectedInstances;
		
	}
	
	/* Retorna un nuevo conjunto de instancias, en donde el atributo attr tiene el valor de attributeValue */
	public static Instances selectInstancesWhere_WithUnknown(Instances S, Attribute attr, double attributeValue) throws Exception{
		int cantFilasTotal = S.attributeStats(0).totalCount;
		/* Este es el indice del atributo dentro de la tabla del arff*/
		int attrIndex = attr.index();
		
		/* Hacemos una copia exacta del conjunto de instancias S */
		Instances selectedInstances = new Instances(S);

		for(int i = 0; i < cantFilasTotal; i++){
			Instance instancia = selectedInstances.instance(i);
			if(instancia != null){
				/* Si la instancia tiene un valor diferente a attributeValue, borramos de la lista(selectedInstances) */
				if( (instancia.value(instancia.attribute(attrIndex)) != attributeValue) ){
					/* Si es que el valor es desconocido, NO lo borramos para luego poder obtener su peso */
					if(!instancia.isMissing(attrIndex)){
						selectedInstances.delete(i);
						i--;
					}
				}
			}else{
				i = cantFilasTotal;
			}
		}

		/* Hacemos una copia exacta del conjunto de instancias S para poder calcular el peso del valor desconocido*/
		Instances instancesCalculateWeight = new Instances(S);
		
		/* Si la instancia esta desconocida debemos setear el peso que tendra dentro de este
		 * nuevo conjuntos de instancias, si la instancia es conocido por default su peso es 1 */
		for(int i = 0; i < selectedInstances.numInstances(); i++){
			Instance instancia = selectedInstances.instance(i);
			
			if(instancia.isMissing(attr)){
				if(instancia.weight() == 1.0)
					instancia.setWeight(calculateWeight(instancesCalculateWeight, attr, attributeValue));
			}
			Log.addMensaje(instancia.toString());
			System.out.println(instancia);
		}

		System.out.println("\n");
		
		return selectedInstances;
		
	}
	
	/* */
	public static double[] selectTiSubsetValue(Instances S, Attribute attr){
		int cantAtributosTotal = S.attributeStats(0).totalCount;
		
		Enumeration instancias = S.enumerateInstances();
		double[] cantFilasPorValor = null;
		
		boolean isMissing = false;
		int isMissingCount = 0; /* Contamos la cantidad de valores desconocidos */
		
		/* Chequeamos si tiene algun valor desconocido */
		for(int i = 0; i < cantAtributosTotal; i++){
			Instance instancia = (Instance)instancias.nextElement();
			if(instancia.isMissing(attr)){
				isMissing = true;
				
				/* Definimos la cantidad de tipos de valores que puede tener el atributo */
				int numValues = 0;
				if(attr.isNumeric())
					numValues = 2;
				else
					numValues = attr.numValues();
					
				cantFilasPorValor = new double[numValues + 1];
				isMissingCount++;
			}
		}
		
		/* Si no hay valores desconocidos no necesitamos un valor extra en el vector */
		if(!isMissing){
			int numValues = 0;
			if(attr.isNumeric()){
				/* Si es numerico son solo dos tipos de valores(false, true) */
				numValues = 2;
			}else{
				numValues = attr.numValues();
			}
			cantFilasPorValor = new double[numValues];
		}
		
		
		instancias = S.enumerateInstances();
		for(int i = 0; i < cantAtributosTotal; i++){
			Instance instancia = (Instance)instancias.nextElement();

			if(!instancia.isMissing(attr)){
				double attrVal = instancia.value(attr);
				cantFilasPorValor[(int)attrVal] +=1;
			}else{
				cantFilasPorValor[cantFilasPorValor.length -1] = isMissingCount;
			}
		}		
		
		
		return cantFilasPorValor;
		
		
	}
	
	/* Retorna la cantidad de casos cubiertos en un grupo de instancias */
	public static double calculateNcasosCubiertos(Instances S){
		
		return S.sumOfWeights();
		
	}
	
	public static int numeroClases(Instances S){
		List<Double> clases = new ArrayList<Double>();
		
		for(int i = 0; i < S.numInstances(); i++){
			Instance instancia = S.instance(i);
			
			if(!clases.contains(instancia.classValue()))
				clases.add(instancia.classValue());
			
		}
		return clases.size();
		
		
	}
	/* Retorna los casos cubiertos equivocadamente en un grupo de instancias */
	/* Suma los pesos de todas las clases menos la de la clase con mayor cantidad de filas */
	public static double calculateEcasosCubiertosErr(Instances S) throws Exception{
		double E = 0.0;
		
		/* Si hay una sola clase retornamos 0 */
		if(Entropy.calculateInfo(S) == 0.0)
			return 0.0;
		
		/* Sumamos la cantidad de filas que tiene cada clase */
		double clases[] = new double[S.numClasses()];
		for(int i = 0; i < S.numInstances(); i++){
			Instance instancia = S.instance(i);
			clases[(int)instancia.classValue()] +=1;
		}
		
		/* Buscamos la clase que tenga mas filas */
		int indiceClasificacionMayor = 0;
		double valorClasificacionMayor = 0.0;
		for(int i =0; i < clases.length; i++){
			if(valorClasificacionMayor < clases[i]){
				indiceClasificacionMayor = i;
				valorClasificacionMayor = clases[i];
			}
		}
		double claseMayor = (double)indiceClasificacionMayor;
		
		/* Sumamos los pesos de todas las clases menos de la claseMayor(o clasificacion) */
		for(int i = 0; i < S.numInstances(); i++){
			Instance instancia = S.instance(i);
			if(instancia.classValue() != claseMayor){
				E += instancia.weight();
			}
		}
		
				
		return E;
	}
	
	/* Buscamos la clase que contenga mas filas */
	/* Buscamos la clase que tenga mas filas */
	public static int indiceClaseConMasFilas(Instances S){
		/* Sumamos la cantidad de filas que tiene cada clase */
		double clases[] = new double[S.numClasses()];
		for(int i = 0; i < S.numInstances(); i++){
			Instance instancia = S.instance(i);
			clases[(int)instancia.classValue()] +=1;
		}

		/* Buscamos la clase que tenga mas filas */
		int indiceClasificacionMayor = 0;
		double valorClasificacionMayor = 0.0;
		for(int i =0; i < clases.length; i++){
			if(valorClasificacionMayor < clases[i]){
				indiceClasificacionMayor = i;
				valorClasificacionMayor = clases[i];
			}
		}

		return indiceClasificacionMayor;
	}
	
	public static String nombreClase(Instances S, int valorClase){
		
		for(int i = 0; i < S.numInstances(); i++){
			Instance instancia = S.instance(i);
			if(instancia.classValue() == valorClase){
				return instancia.classAttribute().value(valorClase);
				//return instancia.classAttribute().value(i);
				
			}
		}
		
		return null;
		
	}

	
	/* Este no se usa, es lo mismo que Entropy(S) == 0*/
	/* Retorna true si la clasificacion es completa(existe una sola clase entre Instances S) */
	public static boolean clasificacionCompleta(Instances S){
		
		Enumeration instancias = S.enumerateInstances();
		int cantFilas = S.numInstances();
		
		/* Chequeamos la cantidad de Clases diferentes que tiene */
		Instance instancia = (Instance)instancias.nextElement();
		double clase1 = instancia.classValue();
		
		for(int i = 0; i < cantFilas - 1; i++){
			instancia = (Instance)instancias.nextElement();
			double clase2 = instancia.classValue();
			if(clase2 != clase1)
				return false;
		}
		
		
			
		return true;
				
		
		
		
	}
	
	
}
